#include "Application.h"

//Complete forward declarations.
#include "SceneManager.h"
#include "GraphicsManager.h"
//Modified: 25/11/13 - Class handles the Graphics Interface
#include "GraphicsInterface.h"
//App manages collision manager
#include "CollisionManager.h"

//Init static data to null.
Application* Application::instance = 0;


LRESULT WINAPI GlobalWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	// Forward hwnd on because we can get messages (e.g., WM_CREATE)
	// before CreateWindow returns, and thus before mhMainWnd is valid
	// This pointer will have been set before the WNDCLASS struct has been given
	// its message processing function pointer. 
	if (Application::IsInit())
		return Application::GetInstance()->WndProc(hwnd, msg, wParam, lParam);
	else
	{
		MessageBox(0, L"Unable to process Windows Message.", 0, 0);
		return DefWindowProc(hwnd, msg, wParam, lParam);
	}
}

LRESULT Application::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch( msg )
	{
		// WM_ACTIVATE is sent when the window is activated or deactivated.  
		// We pause the game when the window is deactivated and unpause it 
		// when it becomes active.  
		case WM_ACTIVATE:
			if( LOWORD(wParam) == WA_INACTIVE )
			{
				appPaused = true;
				mainGameLoopTimer.Stop();
			}
			else
			{
				appPaused = false;
				mainGameLoopTimer.Start();
			}
			return 0;

		// WM_SIZE is sent when the user resizes the window.  
		case WM_SIZE:
			// Save the new client area dimensions.
			clientWidth  = LOWORD(lParam);
			clientHeight = HIWORD(lParam);
			if( wParam == SIZE_MINIMIZED )
			{
				appPaused = true;
				minimized = true;
				maximized = false;
			}
			else if( wParam == SIZE_MAXIMIZED )
			{
				appPaused = false;
				minimized = false;
				maximized = true;
				OnResize();
			}
			else if( wParam == SIZE_RESTORED )
			{
				
				// Restoring from minimized state?
				if( minimized )
				{
					appPaused = false;
					minimized = false;
					OnResize();
				}

				// Restoring from maximized state?
				else if( maximized )
				{
					appPaused = false;
					maximized = false;
					OnResize();
				}
				else if( resizing )
				{
					// If user is dragging the resize bars, we do not resize 
					// the buffers here because as the user continuously 
					// drags the resize bars, a stream of WM_SIZE messages are
					// sent to the window, and it would be pointless (and slow)
					// to resize for each WM_SIZE message received from dragging
					// the resize bars.  So instead, we reset after the user is 
					// done resizing the window and releases the resize bars, which 
					// sends a WM_EXITSIZEMOVE message.
				}
				else // API call such as SetWindowPos or mSwapChain->SetFullscreenState.
				{
					OnResize();
				}
			}
			return 0;

		// WM_EXITSIZEMOVE is sent when the user grabs the resize bars.
		case WM_ENTERSIZEMOVE:
			appPaused = true;
			resizing  = true;
			mainGameLoopTimer.Stop();
			return 0;

		// WM_EXITSIZEMOVE is sent when the user releases the resize bars.
		// Here we reset everything based on the new window dimensions.
		case WM_EXITSIZEMOVE:
			appPaused = false;
			resizing  = false;
			mainGameLoopTimer.Start();
			OnResize();
			return 0;
 
		// WM_DESTROY is sent when the window is being destroyed.
		case WM_DESTROY:
			PostQuitMessage(0);
			return 0;

		// The WM_MENUCHAR message is sent when a menu is active and the user presses 
		// a key that does not correspond to any mnemonic or accelerator key. 
		case WM_MENUCHAR:
			// Don't beep when we alt-enter.
			return MAKELRESULT(0, MNC_CLOSE);

		// Catch this message so to prevent the window from becoming too small.
		case WM_GETMINMAXINFO:
			((MINMAXINFO*)lParam)->ptMinTrackSize.x = 200;
			((MINMAXINFO*)lParam)->ptMinTrackSize.y = 200; 
			return 0;

		case WM_MOUSEMOVE:
			OnMouseMove(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
			return 0;

		case WM_LBUTTONDOWN:
		case WM_MBUTTONDOWN:
		case WM_RBUTTONDOWN:
			OnMouseDown(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), msg);
			return 0;

		case WM_LBUTTONUP:
		case WM_MBUTTONUP:
		case WM_RBUTTONUP:
			OnMouseUp(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), msg);
			return 0;

		case WM_CHAR:
			//Quit the application using the Escape Key - Will be taken
			//out in release build. 
			if (wParam == VK_ESCAPE)
			{
				//D3D doesnt like closing in full screen mode. Thus, 
				//if it currently is in full screen mode, we need
				//to put it back in to windowed mode. 

				//Create a "BOOL" to be set to true if we are in full
				//screen mode. 
				BOOL fsActive = false;
				//Set the above bool by asking the swapChain
				//what state we are in. 
				sharedGraphicsManager->D3DSwapChain()->GetFullscreenState(&fsActive, NULL);
				//Test fsActive. If true, set to windowed mode. 
				if (fsActive)
					sharedGraphicsManager->D3DSwapChain()->SetFullscreenState(false, NULL);

				//Post the quit message. 
				PostQuitMessage(0);
				return 0;
			}

			//else if (wParam == VK_SPACE)
			//{
			//	//put the application to sleep - prevents the application
			//	//cycling through the scenes too fast. 
			//	Sleep (250);
			//	//If space is pressed, increment to the next scene
			//	sharedSceneManager->IncrementToNextScene();
			//}
	}//End switch(msg) {...}

	return DefWindowProc(hwnd, msg, wParam, lParam);
}

Application::Application() 
	: sharedSceneManager(0),
	  sharedGraphicsManager(0),
	  sharedGraphicsInterface(0),
	  sharedCollisionManager(0),

	  appPaused(false),
	  minimized(false),
	  maximized(false),
	  resizing(false),

	  mainWnd(0),
	  initialWindowCaption(L"D3DApplication"),
	  clientHeight(INITIALWINDOWHEIGHT),
	  clientWidth(INITIALWINDOWWIDTH),
	  clientAspectRatio ( (((float)INITIALWINDOWWIDTH) / ((float)INITIALWINDOWHEIGHT)) )
{}

Application::~Application()
{}

Application* Application::GetInstance()
{
	if (!instance)
		instance = new Application();

	return instance;
}

void Application::Release()
{
	//Release scene manager
	if (sharedSceneManager)
	{
		sharedSceneManager->Release();
		sharedSceneManager = NULL;
		OutputDebugStringW(L"[Released Scene Manager]\n");
	}

	//Release the graphics interface - also releases whatever
	//it creates
	if (sharedGraphicsInterface)
	{
		sharedGraphicsInterface->Release();
		sharedGraphicsInterface = NULL;
		OutputDebugStringW(L"[Releasing Graphics Interface]\n");
	}

	//Release graphics manager - also releases ShaderManager and
	//will destroy all of our rendering states. 
	if (sharedGraphicsManager)
	{
		sharedGraphicsManager->Release();
		sharedGraphicsManager = NULL;
		OutputDebugStringW(L"[Released Graphics Manager]\n");
	}

	//Destroys game manager classes
	if (sharedCollisionManager)
	{
		CollisionManager::Release();
		sharedCollisionManager = 0;
		OutputDebugStringW(L"[Released Collision Manager]\n");
	}

	if (instance)
	{
		delete instance;
		instance = 0;
		OutputDebugStringW(L"[Released Application]\n");
	}
}

bool Application::IsInit()
{
	if (instance)
		return true;
	else
		return false;
}

void Application::OnResize()
{
	//Forward OnResize to graphics manager and to the scene manager
	if (sharedGraphicsManager)
		sharedGraphicsManager->OnResize();

	if (sharedGraphicsInterface)
		sharedGraphicsInterface->OnResize();

	if (sharedSceneManager)
		sharedSceneManager->ForwardOnResizeToCurrentScene();
}

void Application::OnMouseDown(WPARAM btnState, int x, int y, UINT originalMessage)
{
	//Simply ask the scene manager to forward the message and data
	//to the correct scene. Same idea for the next two functions
	//aswell. 
	if (sharedSceneManager)
		sharedSceneManager->ForwardOnMouseDownToCurrentScene(btnState, x, y, originalMessage);
}

void Application::OnMouseUp(WPARAM btnState, int x, int y, UINT originalMessage) 
{
	if (sharedSceneManager)
		sharedSceneManager->ForwardOnMouseUpToCurrentScene(btnState, x, y, originalMessage);
}

void Application::OnMouseMove(WPARAM btnState, int x, int y)
{
	if (sharedSceneManager)
		sharedSceneManager->ForwardOnMouseMoveToCurrentScene(btnState, x, y);
}

void Application::Init(HINSTANCE inst)
{
	//static (function scope that retains its value after the function
	//returns) bool indicating if we have (or have not) initilized the Application
	//It would probably be a nightmare if a programmer decided
	//to init this class multiple times.
	static bool init = false;

	if (!init)
	{
		//Sore app handle
		appInstance = inst;

		//Init main window.
		InitMainWindow();

		//Create graphics manager and init
		OutputDebugStringW(L"[Creating Graphics Manager]\n");
		sharedGraphicsManager = GraphicsManager::GetInstance();
		sharedGraphicsManager->Init(mainWnd);

		//Create graphics interface.
		OutputDebugStringW(L"[Creating Graphics Interface]\n");
		sharedGraphicsInterface = GraphicsInterface::GetInstance();

		//Create game manager classes
		OutputDebugStringW(L"[Creating Collision Manager]\n");
		sharedCollisionManager = CollisionManager::GetInstance();

		//Create scene manager (last). Once this is done, we are ready to enter
		//the main game loop.
		OutputDebugStringW(L"[Creating Scene Manager]\n");
		sharedSceneManager = SceneManager::GetInstance();

		//Set init to true once done
		init = true;
	}
}

int Application::Run()
{
	//Main game loop - Translate/dispatch messages to WndProc(), manage the timer
	//,update scene, render scene, calculate/render FPS stats
	MSG msg = {0};

	mainGameLoopTimer.Reset();

	while (msg.message != WM_QUIT)
	{
		//If there windows messages, then process them
		if (PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		//Otherwise, proceed with the game loop - which is a very simple
		//example of a loop. 
		else
		{
			mainGameLoopTimer.Tick();

			if (!appPaused)
			{
				//How long did the last frame take to render?
				CalculateFrameRateStats();

				//Update and then render the scene
				sharedSceneManager->UpdateCurrentSceneWithDelta(mainGameLoopTimer.DeltaTime());
				sharedSceneManager->RenderCurrentScene();
			}
			//Else, game loop is paused. 
			else
				Sleep(100);
		}
	}// while (msg.message != WM_QUIT)

	//Exit the game loop and hand control
	//back to main(). There, we will call the shutdown
	//function of this class in order to clean up our
	//DirectX resources, device, context, etc. 
	return (int)msg.wParam;
}

void Application::InitMainWindow()
{
	//Function that creates the main window.

	WNDCLASS wc;
	wc.style         = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc   = GlobalWndProc; 
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = appInstance;
	wc.hIcon         = LoadIcon(0, IDI_APPLICATION);
	wc.hCursor       = LoadCursor(0, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);
	wc.lpszMenuName  = 0;
	wc.lpszClassName = L"D3DWndClassName";
	
	if( !RegisterClass(&wc) )
	{
		MessageBox(0, L"RegisterClass Failed.", 0, 0);
		exit(1);
	}

	// Compute window rectangle dimensions based on requested client area dimensions.
	// Will be the same values as the initial values specified in the two int consts
	// in the header file. 
	RECT R = { 0, 0, clientWidth, clientHeight };
    AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);
	int width  = R.right - R.left;
	int height = R.bottom - R.top;
	
	//Create the window.
	mainWnd = CreateWindow(L"D3DWndClassName", initialWindowCaption.c_str(), 
		WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 
		width, height, 0, 0, appInstance, 0); 
	
	if( !mainWnd )
	{
		MessageBox(0, L"CreateWindow Failed.", 0, 0);
		exit(2);
	}

	//set aspect ratio
	clientAspectRatio = static_cast<float> ((clientWidth) / (clientHeight));

	//Display window.
	ShowWindow(mainWnd, SW_SHOW);
	UpdateWindow(mainWnd);
}

void Application::CalculateFrameRateStats()
{
	// Code computes the average frames per second, and also the 
	// average time it takes to render one frame.  These stats 
	// are appended to the window caption bar.

	static int frameCnt = 0;
	static float timeElapsed = 0.0f;

	frameCnt++;

	// Compute averages over one second period.
	if( (mainGameLoopTimer.TotalTime() - timeElapsed) >= 1.0f )
	{
		float fps = (float)frameCnt; // fps = frameCnt / 1
		float mspf = 1000.0f / fps;

		std::wostringstream outs;   
		outs.precision(8);
		outs << (sharedSceneManager->ReturnCurrentSceneTitle()) << L"    "
			 << L"FPS: " << fps << L"    " 
			 << L"Frame Time: " << mspf << L" (ms)";
		SetWindowText(mainWnd, outs.str().c_str());
		
		// Reset for next average.
		frameCnt = 0;
		timeElapsed += 1.0f;
	}
}